package cn.bigfire.wedesk.util.portsanner;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class PortScanner {

    /**
     * 1-65534扫描端口TCP|UDP已占用  多线程(1000)   轻量级
     *
     * @param ip ip地址
     * @return IpPorts  ip对应的端口列表
     */
    public static IpPorts scanPortUsedByIp(String ip) {
        return scanPortByIp(ip, 1000, true);
    }

    /**
     *  1-65534扫描端口TCP已占用  多线程(8000)     重量级
     *
     * @param ip ip地址
     * @return IpPorts  ip对应的端口列表
     */
    public static IpPorts scanPortAlreadyByIp(String ip) {
        return scanPortByIp(ip, 8000, false);
    }

    /**
     * 1-65534 扫描端口TCP已占用  多线程
     * 线程数 可能受 cpu数 和 网络有线无线确定,网卡性能等影响。
     * 目前 fast模式下推荐线程数=1000  普通模式下推荐7500-8000
     *
     * @param ip ip地址
     * @param threadNum 线程数
     * @param fast 快速
     * @return IpPort  ip对应的端口列表
     */
    private static IpPorts scanPortByIp(String ip, int threadNum, boolean fast) {
        IpPorts ipPorts = IpPorts.of(ip);
//        int threadNum = 8500; // 并发线程数
        int timeout = NetUtil.isInnerIP(ip) ? 100 : 5000; //超时，毫秒 内网100，外网5000 (外网不推荐批量扫描。效果不好，还是建议单个端口扫描)
        try {
            ExecutorService executor = Executors.newFixedThreadPool(threadNum);
            long start = System.currentTimeMillis();
            AtomicInteger atomicInteger = new AtomicInteger(1);
            for (int i = 1; i <= 65534; i++) {
                executor.submit(() -> {
                    try {
                        int port = atomicInteger.getAndIncrement();
                        if (fast) {
                            if (!NetUtil.isUsableLocalPort(port)) {//推荐使用
                                ipPorts.getPortList().add(port);
                            }
                        } else {
                            InetSocketAddress inetSocketAddress = NetUtil.buildInetSocketAddress(ip, port);
                            if (NetUtil.isOpen(inetSocketAddress, timeout)) {
                                ipPorts.getPortList().add(port);
                            }
                        }
                    } catch (Exception e) {
                        System.err.printf("NetUtil.isOpen has err:" + e.getMessage());
                    }
                });
            }
            //任务提交完就安全关闭。
            executor.shutdown();
            boolean isSafeOut = executor.awaitTermination(10, TimeUnit.SECONDS);
            long end = System.currentTimeMillis();
            long duration = end - start;
            if (!isSafeOut) {
                System.out.println(threadNum + "线程 端口扫描 耗时:" + duration + "毫秒 完整:" + isSafeOut);
            }
        } catch (Exception e) {
            System.err.printf("scanPortByIp has err:" + e.getMessage());
        }
        return ipPorts;
    }


    /**
     * 多线程 扫描局域网那些ip开放了某个端口
     * 推荐：线程数=254(各种机器都以测试均推荐254个线程)
     * @param openPort 端口号
     * @return PortIps port对应的ip列表
     */
    public static PortIps scanIpByPort(int openPort) {
        PortIps portIps = PortIps.of(openPort);
        int threadNum = 254; // 并发线程数
        String myIp = NetUtil.getLocalhostStr();
        String subnet = StrUtil.subBefore(myIp, ".", true);
        try {
            ExecutorService executor = Executors.newFixedThreadPool(threadNum);
            long start = System.currentTimeMillis();
            for (int i = 1; i <= 254; i++) {
                final String ip = subnet + "." + i;
                executor.submit(() -> {
                    try {
                        InetSocketAddress inetSocketAddress = NetUtil.buildInetSocketAddress(ip, openPort);
                        if (NetUtil.isOpen(inetSocketAddress, 100)) {
                            portIps.getIpList().add(ip);
                        }
                    } catch (Exception e) {
                        System.err.printf("NetUtil.isOpen has err:" + e.getMessage());
                    }
                });
            }
            //任务提交完就安全关闭。
            executor.shutdown();
            boolean isSafeOut = executor.awaitTermination(10, TimeUnit.SECONDS);
            long end = System.currentTimeMillis();
            if (!isSafeOut) {
                System.out.println(threadNum + "线程 ip扫描 耗时:" + (end - start) + "毫秒 完整:" + isSafeOut);
            }
        } catch (Exception e) {
            System.err.printf("scanIpByPort has err:" + e.getMessage());
        }
        return portIps;
    }



}
